package org.wattdepot.cli.processor;

import org.wattdepot.cli.command.ChartCommand;
import org.wattdepot.cli.command.DailySensorData;
import org.wattdepot.cli.command.FuelType;
import org.wattdepot.cli.command.HelpCommand;
import org.wattdepot.cli.command.ListSensorStamp;
import org.wattdepot.cli.command.ListSources;
import org.wattdepot.cli.command.ListSummary;
import org.wattdepot.cli.command.PowerStamp;
import org.wattdepot.cli.command.PowerStats;
import org.wattdepot.cli.command.TotalGenerated;
import org.wattdepot.cli.command.TotalPower;
import org.wattdepot.cli.command.WattDepotCliCommand;
import org.wattdepot.client.WattDepotClient;
import org.wattdepot.client.WattDepotClientException;
import org.wattdepot.resource.source.jaxb.Source;
import org.wattdepot.util.tstamp.Tstamp;
import java.util.List;
import javax.xml.datatype.XMLGregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.ArrayList;

/**
 * A command line interface to WattDepot that shows basic connection and processing. Command
 * specifications can be found at
 * http://code.google.com/p/wattdepot-cli/wiki/CommandSpecificationVersionTwo.
 * 
 * @author Bao Huy Ung, Edward Meyer
 */
public class CommandLineInterface {
  /** The URL to the public WattDepot Server. */
  protected String hostUri = "http://server.wattdepot.org:8182/wattdepot/";
  /** Holds an instance of the WattDepot client. */
  protected WattDepotClient client;
  /** Carriage return on this operating system. */
  protected String cr = System.getProperty("line.separator");

  /**
   * Connect to the WattDepot server.
   */
  public CommandLineInterface() {
    client = new WattDepotClient(this.hostUri);
  }

  /**
   * The main program that is invoked from 'java -jar wattdepot-cli.jar'. Opens a connection to the
   * WattDepot server and parses input on the command line calling the appropriate method to carry
   * out the command and outputs the results on the command line.
   * 
   * @param args All args are ignored.
   * @throws WattDepotClientException If anything goes wrong.
   */
  public static void main(String[] args) throws WattDepotClientException {

    // A map that will direct the input commands to their respective classes.
    Map<String, WattDepotCliCommand> commandMap = new HashMap<String, WattDepotCliCommand>();

    // Create variables that will be used to read in the commands from the user.
    Scanner scanCommand = null;
    Scanner userInput = null;
    String userCommand = null;
    List<String> argList = null;
    WattDepotCliCommand commandInstance = null;

    // Initialize the command map.
    commandMap.put("help", new HelpCommand());
    commandMap.put("chart", new ChartCommand());
    commandMap.put("sources", new ListSources());
    commandMap.put("summary", new ListSummary());
    commandMap.put("sensordata", new ListSensorStamp());
    commandMap.put("dailysensordata", new DailySensorData());
    commandMap.put("power", new PowerStamp());
    commandMap.put("powerstats", new PowerStats());
    commandMap.put("total", new TotalGenerated());
    commandMap.put("fueltypes", null);
    commandMap.put("totalpower", new TotalPower());
    commandMap.put("carboncontent", null);
    commandMap.put("fuel", new FuelType());

    System.out.println("Starting CLI.");

    // Establish connection to the WattDepot server.
    CommandLineInterface cli = new CommandLineInterface();
    // Stop right away if we cannot contact server.
    if (!cli.client.isHealthy()) {
      System.out.println("Failed to connect to " + cli.client.getWattDepotUri());
      return;
    }
    // Indicate successful connection.
    System.out.println("Connected to " + cli.client.getWattDepotUri());

    // Continually run the CLI until the user quits
    while (true) {
      // Command prompt.
      System.out.print("> ");

      // Get the command entered by the user.
      userInput = new Scanner(System.in);
      userCommand = userInput.nextLine();

      // Parse the command into an ArrayList of strings for easier access of arguments.
      scanCommand = new Scanner(userCommand);
      scanCommand.useDelimiter(" ");
      argList = new ArrayList<String>();
      while (scanCommand.hasNext()) {
        argList.add(scanCommand.next());
      }

      // As long as the user doesn't hit enter w/o entering text, get the first argument.
      if (!argList.isEmpty()) {
        commandInstance = commandMap.get(argList.get(0));
      }

      // If the user just hit enter, print nothing.
      if (argList.isEmpty()) {
        System.out.print("");
      }
      else if (argList.get(0).equalsIgnoreCase("quit")) {
        break;
      }
      // If the user entered a command that wasn't recognized by the map.
      else if (commandInstance == null) {
        System.out.println("Command \"" + argList.get(0) + "\" is not recognized by this sytem.");
      }
      // Else, the command entered was valid, output the results.
      else {
        String output = commandInstance.doCommand(argList);
        System.out.println(output);
      }
    }
    System.out.println("Exiting CLI.");
  }

  /**
   * Helper method that checks if a source name exists in the server.
   * 
   * @param name Is the name of the source to be looked up.
   * @return The Source object if found, or null if it doesn't exist.
   */
  protected Source isValidSource(String name) {
    try {
      return client.getSource(name);
    }
    catch (Exception e) {
      return null;
    }
  }

  /**
   * Helper method that checks if a timestamp entered could be parsed into a XMLGregorian object.
   * 
   * @param inputStamp The entered timestamp by the user.
   * @return The parsed XMLGregorianCalendar timestamp or null if failed.
   */
  protected XMLGregorianCalendar isValidTimestamp(String inputStamp) {
    try {
      return Tstamp.makeTimestamp(inputStamp);
    }
    catch (Exception e) {
      return null;
    }
  }

  /**
   * Helper method that takes in a virtual source, then recursively searches through all it's
   * subsources adding each non-virtual source to a list.
   * 
   * @param inputSource Is a virtual source in which the method will search for non-virtual
   * subsources.
   * @param subs Is the list of Sources to be returned after the method completes
   * @return sub The list of all non-virtual subsources.
   * @throws Exception If anything goes wrong during the recursion process.
   */
  protected List<Source> getNonVirtuals(Source inputSource, List<Source> subs) throws Exception {
    // Recursively search through each subsource of inputSource looking for all non-virtual
    // subsources. Uses Hrefs because there is no other method to get names from getSubSources
    // method.
    for (String testSubHrefs : inputSource.getSubSources().getHref()) {

      // Get a new subsource from inputSource and test if it's a virtual source.
      // Need to use substring to grab the very last end of the Href which contains the name of the
      // source.
      Source newSub = client.getSource(testSubHrefs.substring(testSubHrefs.indexOf("SIM")));

      // If it is a virtual source recurse the method.
      if (newSub.isVirtual()) {
        getNonVirtuals(newSub, subs);
      }
      else {
        // Otherwise add the non-virtual source to the list.
        subs.add(newSub);
      }
    }
    // Once the recursion process completes, subs will be a list of non-virtual subsources.
    return subs;
  }
}
